defmodule ProgContest.Evaluation do
  use GenServer

  alias ProgContest.{BlockingQueue, DockerImages}

  @type mf :: {module, function}

  @name __MODULE__

  @doc """
  The callback will be called when evaluation ends.
  """
  @spec start_link(mf) :: {:ok, pid} | {:error, term}
  def start_link(callback) do
    GenServer.start_link(__MODULE__, callback, name: @name)
  end

  def evaluate(quiz_id, username, language, code, {mod, fun}) do
    case DockerImages.find_docker_image(quiz_id, language) do
      nil -> nil
      image ->
        tmp_path = "#{System.tmp_dir!()}/#{Ecto.UUID.generate()}"
        try do
          :ok = File.write!(tmp_path, code, [:write, :binary])
          {time, result} = :timer.tc fn->
            Rambo.run("docker", ~w[run --rm -v #{tmp_path}:#{image.code_mounting_point} #{image.image_id}])
          end
          apply(mod, fun, [quiz_id, username, result, time])
        after
          File.rm_rf!(tmp_path)
        end
    end
  end

  def evaluate_later(item) do
    BlockingQueue.push(item)
  end

  @impl true
  @spec init(any) :: {:ok, any, {:continue, :evaluate}}
  def init(callback) do
    {:ok, callback, {:continue, :evaluate}}
  end

  @impl true
  def handle_continue(:evaluate, callback) do
    {quiz_id, username, language, code} = BlockingQueue.pop()
    evaluate(quiz_id, username, language, code, callback)
    {:noreply, callback, {:continue, :evaluate}}
  end
end
